#include "sobjc-commons/type.h"
#include "sobjc-commons/defines.h"
#include "sobjc-commons/str_stream.h"

static char prim2code_table[] = {
  OBJC_TYPE_CHAR_CODE,
  OBJC_TYPE_UCHAR_CODE,
  OBJC_TYPE_SHORT_CODE,
  OBJC_TYPE_USHORT_CODE,
  OBJC_TYPE_INT_CODE,
  OBJC_TYPE_UINT_CODE,
  OBJC_TYPE_LONG_CODE,
  OBJC_TYPE_ULONG_CODE,
#ifdef HAVE_LONG_LONG
  OBJC_TYPE_LONGLONG_CODE,
  OBJC_TYPE_ULONGLONG_CODE,
#endif
  OBJC_TYPE_FLOAT_CODE,
  OBJC_TYPE_DOUBLE_CODE,
  OBJC_TYPE_BOOL_CODE,
  OBJC_TYPE_VOID_CODE,
  OBJC_TYPE_STR_CODE,
  OBJC_TYPE_ID_CODE,
  OBJC_TYPE_CLASS_CODE,
  OBJC_TYPE_SEL_CODE,
  OBJC_TYPE_UNKNOWN_CODE
};

static void type_list_to_code_on(objc_type_list_t, objc_str_stream_t);
static void type_to_code_on(objc_type_t, objc_str_stream_t);

objc_type_t
objc_make_type()
{
  objc_type_t type;

  type = ALLOC(struct objc_type);
  MEMZERO(type, struct objc_type, 1);
  type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
  return type;
}

void
objc_free_type(objc_type_t type)
{
  if (type != NULL) {
    switch (type->code_type) {
    case OBJC_TYPE_ARRAY_CODE_TYPE:
      objc_free_type(type->body.array.elem);
      break;

    case OBJC_TYPE_STRUCT_CODE_TYPE:
    case OBJC_TYPE_UNION_CODE_TYPE:
      objc_type_list_free(type->body.su.mems);
      break;

    case OBJC_TYPE_PRIM_CODE_TYPE:
    case OBJC_TYPE_BITFIELD_CODE_TYPE:
      break;
    }
    objc_free(type);
  }
}

objc_type_list_t
objc_make_type_list_from_code(const char *code)
{
  return objc_make_type_list_from_codep(&code);
}

objc_type_list_t
objc_make_type_list_from_codep(const char **code)
{
  objc_type_list_t types = NULL;
  objc_type_t type;

  while (**code) {
    type = objc_make_type_from_codep(code);
    if (type != NULL)
      types = objc_type_list_add(types, type);
    else {
      objc_type_list_free(types);
      return NULL;
    }
  }
  return types;
}

objc_type_t
objc_make_type_from_code(const char *code)
{
  return objc_make_type_from_codep(&code);
}

#define CASE_CODE(name)                                 \
  case OBJC_TYPE_##name##_CODE:                         \
    type->body.prim = OBJC_TYPE_##name##_PRIM_TYPE;     \
    (*code)++;                                            \
    return type

#define CASE_QUAL(name)                     \
  case OBJC_TYPE_##name##_CODE:             \
    type->quals |= OBJC_TYPE_##name##_QUAL; \
    break

objc_type_t
objc_make_type_from_codep(const char **code)
{
  objc_type_t type, subtype;
  BOOL is_st, indirect = NO;
  const char *begin;
  size_t len;

  type = objc_make_type();
  for (;;) {
    switch (**code) {
    case 0:
      return type;

    CASE_CODE(CHAR);
    CASE_CODE(UCHAR);
    CASE_CODE(SHORT);
    CASE_CODE(USHORT);
    CASE_CODE(INT);
    CASE_CODE(UINT);
    CASE_CODE(LONG);
    CASE_CODE(ULONG);
#ifdef HAVE_LONG_LONG
    CASE_CODE(LONGLONG);
    CASE_CODE(ULONGLONG);
#endif
    CASE_CODE(FLOAT);
    CASE_CODE(DOUBLE);
    CASE_CODE(BOOL);
    CASE_CODE(VOID);
    CASE_CODE(STR);
    CASE_CODE(ID);
    CASE_CODE(CLASS);
    CASE_CODE(SEL);
    CASE_CODE(UNKNOWN);

    CASE_QUAL(CONST);
    CASE_QUAL(IN);
    CASE_QUAL(INOUT);
    CASE_QUAL(OUT);
    CASE_QUAL(BYCOPY);
    CASE_QUAL(BYREF);
    CASE_QUAL(ONEWAY);

    case OBJC_TYPE_PTR_CODE:
      type->nptrs++;
      break;

    case OBJC_TYPE_BITFIELD_CODE:
      type->code_type = OBJC_TYPE_BITFIELD_CODE_TYPE;
      type->body.nbits = 0;
      while (isdigit(*++*code)) {
        type->body.nbits *= 10;
        type->body.nbits += **code - '0';
      }
      return type;

    case '[':
      type->code_type = OBJC_TYPE_ARRAY_CODE_TYPE;
      type->body.array.nelems = 0;
      (*code)++; /* '[' */
      while (isdigit(**code)) {
        type->body.array.nelems *= 10;
        type->body.array.nelems += **code - '0';
        (*code)++;
      }
      type->body.array.elem = objc_make_type_from_codep(code);
      if (type->body.array.elem != NULL && **code == ']') {
        (*code)++; /* ']' */
        return type;
      } else
        goto parse_error;

    case '{': case '(':
      is_st = **code == '{';
      type->code_type = is_st ? OBJC_TYPE_STRUCT_CODE_TYPE :
        OBJC_TYPE_UNION_CODE_TYPE;
      (*code)++; /* '{' or '(' */

      /* type name */
      begin = *code;
      while (**code) {
        if (**code == '=') {
          if (type->nptrs < 2) {
            (*code)++;
            break;
          } else
            goto parse_error;
        } else if ((is_st && **code == '}') || (!is_st && **code == ')')) {
          if (type->nptrs >= 2) {
            (*code)++;
            indirect = YES;
            break;
          } else
            goto parse_error;
        } 
        (*code)++;
      }

      len = (size_t)(*code - begin - 1);
      type->body.su.name = ALLOC_N(char, len+1);
      strncpy(type->body.su.name, begin, len);
      type->body.su.name[len] = 0;
      if (indirect)
        return type;

      /* members */
      type->body.su.mems = NULL;
      for (;;) {
        if (!**code)
          goto parse_error;
        else if ((is_st && **code == '}') || (!is_st && **code == ')')) {
          (*code)++; /* '}' or ')' */
          break;
        }

        subtype = objc_make_type_from_codep(code);
        if (subtype != NULL)
          type->body.su.mems = objc_type_list_add(type->body.su.mems, subtype);
        else
          goto parse_error;
      }
      return type;
   
    default:
      goto parse_error;
    }
    (*code)++;
  }

parse_error:
  objc_free_type(type);
  return NULL;
}

char *
objc_type_list_to_full_code(objc_type_list_t types)
{
  objc_str_list_t codes;
  char *full_code;

  codes = objc_type_list_to_code_list(types);
  full_code = objc_str_list_join(codes, "");
  objc_str_list_free(codes);
  return full_code;
}

objc_str_list_t
objc_type_list_to_code_list(objc_type_list_t types)
{
  objc_str_stream_t st;
  objc_str_list_t codes = NULL;

  if (types == NULL)
    return NULL;

  while (types != NULL) {
    st = objc_make_str_stream();
    type_to_code_on(types->car, st);
    objc_str_list_add_set(&codes, objc_str_stream_copy_value_and_free(st));
    types = types->cdr;
  }

  return codes;
}

void
type_list_to_code_on(objc_type_list_t types, objc_str_stream_t st)
{
  if (types == NULL)
    return;

  while (types != NULL) {
    type_to_code_on(types->car, st);
    types = types->cdr;
  }
}

static __inline__ void
write_num(objc_str_stream_t st, int val)
{
  while (val / 10 != 0) {
    objc_str_stream_writec(st, val / 10 + '0');
    val = val % 10;
  }
  objc_str_stream_writec(st, val + '0');
}

static void
type_to_code_on(objc_type_t type, objc_str_stream_t st)
{
  int i;

  if (type == NULL)
    return;

#define WRITE_QUAL(name)                                    \
  do {                                                      \
    if (type->quals & OBJC_TYPE_##name##_QUAL)              \
      objc_str_stream_writec(st, OBJC_TYPE_##name##_CODE);  \
  } while (0)

  if (st == NULL)
    st = objc_make_str_stream();

  WRITE_QUAL(CONST);
  WRITE_QUAL(IN);
  WRITE_QUAL(INOUT);
  WRITE_QUAL(OUT);
  WRITE_QUAL(BYCOPY);
  WRITE_QUAL(BYREF);
  WRITE_QUAL(ONEWAY);

  for (i = 0; i < type->nptrs; i++)
    objc_str_stream_writec(st, OBJC_TYPE_PTR_CODE);

  switch (type->code_type) {
  case OBJC_TYPE_PRIM_CODE_TYPE:
    objc_str_stream_writec(st, prim2code_table[type->body.prim]);
    break;

  case OBJC_TYPE_ARRAY_CODE_TYPE:
    objc_str_stream_writec(st, '[');
    write_num(st, type->body.array.nelems);
    type_to_code_on(type->body.array.elem, st);
    objc_str_stream_writec(st, ']');
    break;

  case OBJC_TYPE_STRUCT_CODE_TYPE:
  case OBJC_TYPE_UNION_CODE_TYPE:
    if (type->code_type == OBJC_TYPE_STRUCT_CODE_TYPE)
      objc_str_stream_writec(st, '{');
    else
      objc_str_stream_writec(st, '(');

    objc_str_stream_write(st, type->body.su.name);
    if (type->nptrs < 2) {
      objc_str_stream_writec(st, '=');
      type_list_to_code_on(type->body.su.mems, st);
    }

    if (type->code_type == OBJC_TYPE_STRUCT_CODE_TYPE)
      objc_str_stream_writec(st, '}');
    else
      objc_str_stream_writec(st, ')');
    break;

  case OBJC_TYPE_BITFIELD_CODE_TYPE:
    objc_str_stream_writec(st, 'b');
    write_num(st, type->body.nbits);
    break;
  }
}

size_t
objc_type_list_get_type_size(objc_type_list_t types)
{
  size_t size = 0;

  while (types) {
    size += objc_type_get_type_size(types->car);
    types = types->cdr;
  }

  return size;
}

size_t
objc_type_get_type_size(objc_type_t type)
{
  size_t max, size;
  objc_type_list_t mems;

  if (type->nptrs > 0)
    return sizeof(void *);

  switch (type->code_type) {
  case OBJC_TYPE_PRIM_CODE_TYPE:
    switch (type->body.prim) {
    case OBJC_TYPE_CHAR_PRIM_TYPE:
      return sizeof(char);
    case OBJC_TYPE_UCHAR_PRIM_TYPE:
      return sizeof(unsigned char);
    case OBJC_TYPE_SHORT_PRIM_TYPE:
      return sizeof(short);
    case OBJC_TYPE_USHORT_PRIM_TYPE:
      return sizeof(unsigned short);
    case OBJC_TYPE_INT_PRIM_TYPE:
      return sizeof(int);
    case OBJC_TYPE_UINT_PRIM_TYPE:
      return sizeof(unsigned int);
    case OBJC_TYPE_LONG_PRIM_TYPE:
      return sizeof(long);
    case OBJC_TYPE_ULONG_PRIM_TYPE:
      return sizeof(unsigned long);
#ifdef HAVE_LONG_LONG
    case OBJC_TYPE_LONGLONG_PRIM_TYPE:
      return sizeof(long long);
    case OBJC_TYPE_ULONGLONG_PRIM_TYPE:
      return sizeof(unsigned long long);
#endif
    case OBJC_TYPE_FLOAT_PRIM_TYPE:
      return sizeof(float);
    case OBJC_TYPE_DOUBLE_PRIM_TYPE:
      return sizeof(double);
    case OBJC_TYPE_BOOL_PRIM_TYPE:
      return sizeof(BOOL);
    case OBJC_TYPE_VOID_PRIM_TYPE:
      return 0;
    case OBJC_TYPE_STR_PRIM_TYPE:
      return sizeof(char *);
    case OBJC_TYPE_ID_PRIM_TYPE:
    case OBJC_TYPE_CLASS_PRIM_TYPE:
    case OBJC_TYPE_SEL_PRIM_TYPE:
    case OBJC_TYPE_UNKNOWN_PRIM_TYPE:
      return sizeof(void *);
    }

  case OBJC_TYPE_ARRAY_CODE_TYPE:
    if (type->body.array.nelems > 0)
      return type->body.array.nelems *
        objc_type_get_type_size(type->body.array.elem);
    else
      return sizeof(void *);

  case OBJC_TYPE_BITFIELD_CODE_TYPE:
    return type->body.nbits;

  case OBJC_TYPE_STRUCT_CODE_TYPE:
    return objc_type_list_get_type_size(type->body.su.mems);

  case OBJC_TYPE_UNION_CODE_TYPE:
    max = 0;
    mems = type->body.su.mems;
    while (mems) {
      size = objc_type_get_type_size(mems->car);
      if (size > max)
        max = size;
    }
    return max;
  }

  /* error */
  return 0;
}

objc_type_encoding_t
objc_make_type_encoding(objc_type_list_t types)
{
  objc_type_encoding_t enc;

  enc = ALLOC(objc_type_encoding);
  enc->types = types;
  enc->ncodes = objc_type_list_get_size(types);
  enc->codes = objc_type_list_to_code_list(types);
  enc->full_code = objc_str_list_join(enc->codes, "");

  return enc;
}

const char *
objc_type_encoding_get_code(objc_type_encoding_t enc, int index)
{
  return objc_str_list_get(enc->codes, index);
}

size_t
objc_type_encoding_get_frame_size(objc_type_encoding_t enc)
{
  return objc_type_list_get_type_size(enc->types);
}

const char *
objc_type_encoding_get_method_return_code(objc_type_encoding_t enc)
{
  return objc_type_encoding_get_code(enc, 0);
}

size_t
objc_type_encoding_get_method_return_size(objc_type_encoding_t enc)
{
  return objc_type_get_type_size(enc->types->car);
}

